import constants from '@/constants';
import { BaseService } from '@/core/base.service';
import { NbcbService } from '@/core/nbcb.service';
import { SandService } from '@/core/sand.service';
import { UopService } from '@/core/uop.service';
import { UtilService } from '@/core/util.service';
import {
  BillStatusEnum,
  BillTypeEnum,
  UopPaymentTypeEnum,
  UopPaymentTypeEnumMaps,
  WalletOrderStatusEnum,
} from '@/enums';
import { BillService } from '@/services/bill.service';
import { halt } from '@/utils';
import { InjectQueue, Process, Processor } from '@nestjs/bull';
import { OnModuleInit } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { Job, Queue } from 'bull';
import dayjs from 'dayjs';

@Processor('bill')
export class BillProcessor extends BaseService implements OnModuleInit {
  constructor(
    @InjectQueue('bill')
    private readonly billQueue: Queue,
    private readonly uopService: UopService,
    private readonly sandService: SandService,
    private readonly nbcbService: NbcbService,
    private readonly billService: BillService,
    private readonly utilService: UtilService,
  ) {
    super();
  }

  async onModuleInit() {
    await this.utilService.cleanKeys(`bull:bill:repeat`);
    // await this.billQueue.add('withdraw', null, {
    //   repeat: {
    //     every: 10 * 1000,
    //   },
    //   jobId: 'bill-withdraw',
    // });
    // await this.billQueue.add('check', null, {
    //   repeat: {
    //     every: 10 * 1000,
    //   },
    //   jobId: 'bill-check',
    // });
    // await this.billQueue.add('tz', null, {
    //   repeat: {
    //     every: 60 * 1000,
    //   },
    //   jobId: 'bill-tz',
    // });
    await this.billQueue.add('refund', null, {
      repeat: {
        every: 60 * 60 * 1000,
      },
      jobId: 'bill-refund',
    });
    await this.billQueue.add('polling', null, {
      repeat: {
        every: 30000,
      },
      jobId: 'bill-polling',
    });
  }

  @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
  async tzEveryDayAtMidnight() {
    const [{ amt }] = await this.prisma.$queryRawUnsafe<any>(`
      SELECT SUM(t.balance) AS amt
      FROM User AS t   
      WHERE t.id != ${constants.default.ownerId}   
    `);
    const date = dayjs().format('YYYY-MM-DD');
    await this.prisma.fundSummary.upsert({
      where: {
        date,
      },
      update: {
        amt,
        date,
      },
      create: {
        amt,
        date,
      },
    });
  }

  @Cron(CronExpression.EVERY_30_MINUTES)
  async tzEvery30Minutes() {
    const date = dayjs().format('YYYY-MM-DD HH:mm:ss');
    console.log(`${date} - 同步账单时区`);
    if (!!constants.isDev) {
      return;
    }
    // 用于记录账单的东八区日期
    await this.prisma.$executeRawUnsafe(`
      UPDATE bill AS t
      SET t.date = DATE_FORMAT(CONVERT_TZ(t.createdAt, 'UTC', 'Asia/Shanghai'), '%Y-%m-%d')
      WHERE t.createdAt >= '${dayjs().subtract(3, 'hours').format('YYYY-MM-DD 00:00:00')}'
    `);
  }

  @Cron(CronExpression.EVERY_HOUR)
  async tzEveryHour() {
    const date = dayjs().format('YYYY-MM-DD HH:mm:ss');
    console.log(`${date} - 同步账单时区`);
    if (!!constants.isDev) {
      return;
    }
    // 用于记录账单的东八区日期
    await this.prisma.$executeRawUnsafe(`
      UPDATE bill AS t
      SET t.date = DATE_FORMAT(CONVERT_TZ(t.createdAt, 'UTC', 'Asia/Shanghai'), '%Y-%m-%d')
      WHERE t.createdAt >= '${dayjs().subtract(3, 'days').format('YYYY-MM-DD 00:00:00')}'
    `);
  }

  @Process('withdraw')
  async withdraw() {
    const date = dayjs().format('YYYY-MM-DD');
    console.log(`${date} - 提现订单查询`);

    if (!!constants.isDev) {
      return;
    }

    const withdrawLogs = await this.prisma.bill.findMany({
      where: {
        type: BillTypeEnum.WITHDRAW,
        status: BillStatusEnum.PENDING,
        isDeleted: false,
      },
      take: 20,
      orderBy: {
        createdAt: 'asc',
      },
    });

    for (let log of withdrawLogs) {
      try {
        await this.billService.withdraw({ id: log.id });
      } catch (e) {
        console.log(e);
      }
    }
  }

  @Process('check')
  async check() {
    const date = dayjs().format('YYYY-MM-DD HH:mm:ss');
    console.log(`${date} - 提现订单检查`);

    if (!!constants.isDev) {
      return;
    }
    try {
      // 用于记录账单的东八区日期
      await this.prisma.$executeRawUnsafe(`
      UPDATE bill AS t 
      SET t.date = DATE_FORMAT(CONVERT_TZ(t.createdAt, 'UTC', 'Asia/Shanghai'), '%Y-%m-%d')
      WHERE 
          DATE_FORMAT(CONVERT_TZ(t.createdAt, 'UTC', 'Asia/Shanghai') , '%Y-%m-%d') <= '${dayjs().subtract(12, 'hours').format('YYYY-MM-DD HH:mm:ss')}'; 
    `);
    } finally {
      const checkLogs = await this.prisma.bill.findMany({
        where: {
          type: BillTypeEnum.WITHDRAW,
          status: BillStatusEnum.PROCESSING,
          isDeleted: false,
        },
        take: 20,
        orderBy: {
          createdAt: 'asc',
        },
      });

      for (let log of checkLogs) {
        try {
          await this.billService.check({ id: log.id });
        } catch (e) {
          console.log(e);
        }
      }
    }
  }

  @Process('refund')
  async refund() {
    const date = dayjs().format('YYYY-MM-DD');
    console.log(`${date} - 退汇订单查询`);

    if (!!constants.isDev) {
      return;
    }

    const res = await this.nbcbService.queryRefund({ date });

    if (!res || res?.Data?.retCode !== '0000') {
      return halt(`退汇查询失败: ${res?.Data?.retMsg ?? '未知错误'}`);
    }

    const refundDtls = res?.Data?.refundDtls ?? [];

    for (let o of refundDtls) {
      await this.prisma.refundBill.upsert({
        where: {
          refundCoreNo: o.refundCoreNo,
        },
        update: {
          ...o,
        },
        create: {
          ...o,
          createdAt: new Date(date),
        },
      });
    }
  }

  @Process('polling')
  async polling() {
    console.log(`${dayjs().format('YYYY-MM-DD HH:mm:ss')} - 充值订单轮询`);
    try {
      const orders = await this.prisma.bill.findMany({
        where: {
          type: BillTypeEnum.RECHARGE,
          status: WalletOrderStatusEnum.PENDING,
          isDeleted: false,
          createdAt: {
            lte: dayjs().subtract(5, 'minutes').toDate(),
          },
        },
      });
      for (let o of orders) {
        console.log(
          `充值订单 ${o.no} ${dayjs(o.createdAt).format('YYYY-MM-DD HH:mm:ss')}`,
        );
        await this.billQueue.add('cancel', {
          no: o.no,
        });
      }
    } catch (e) {
      console.log(e);
    }
  }

  @Process('callback')
  async callback(job: Job<{ no: string }>) {
    const { no } = job.data;
    console.log(`商订单回调: ${no}`);
    await this.checkOrder(no);
  }

  @Process('cancel')
  async cancel(job: Job<{ no: string }>) {
    const { no } = job.data;
    console.log(`充值订单取消: ${no}`);
    await this.checkOrder(no);
    await this.handleOrderCancel(no);
  }

  async handleOrderCancel(no: string) {
    await this.prisma.bill.update({
      where: {
        no,
      },
      data: {
        status: BillStatusEnum.CANCELED,
      },
    });
  }

  async handleOrderCallback(no: string) {
    const order = await this.prisma.bill.findFirst({
      where: {
        no,
        status: BillStatusEnum.PENDING,
      },
      select: {
        id: true,
        total: true,
        owner: {
          select: {
            id: true,
            version: true,
          },
        },
      },
    });
    if (!order) {
      return;
    }
    try {
      return await this.redlock.using([`bill:${no}`], 10000, {}, async () => {
        await this.prisma.$transaction(async (tx) => {
          await tx.bill.update({
            where: {
              id: order.id,
            },
            data: {
              status: BillStatusEnum.COMPLETED,
            },
          });
          const owner = await tx.user.findUnique({
            where: {
              id: order.owner.id,
            },
          });
          await tx.user.update({
            where: {
              id: owner.id,
              version: owner.version,
            },
            data: {
              version: {
                increment: 1,
              },
              balance: {
                increment: Number(order.total),
              },
            },
          });
        });
      });
    } catch {
      await this.billQueue.add(
        'cancel',
        {
          no,
        },
        {
          delay: 1000,
        },
      );
    }
  }

  private async checkOrder(no: string) {
    const order = await this.prisma.bill.findFirst({
      where: {
        no,
        type: BillTypeEnum.RECHARGE,
        status: BillStatusEnum.PENDING,
      },
    });
    if (!order) {
      return halt(`充值订单${no}信息异常`);
    }
    console.log(
      `充值订单检查: ${no} - ${UopPaymentTypeEnumMaps[order.payType]}`,
    );
    if (order.payType === UopPaymentTypeEnum.SAND) {
      try {
        const result = await this.sandService.query(order.no);
        console.log(
          `杉徳充值订单${no}状态: `,
          result?.head?.respCode,
          result?.head?.respMsg,
        );
        if (!!result) {
          if (result?.head?.respCode === '000000') {
            if (result?.body?.orderStatus === '00') {
              console.log(`充值订单${no}已支付不可取消`);
              await this.handleOrderCallback(no);
              return halt(`充值订单${no}已支付不可取消`);
            }
            if (result?.body?.orderStatus !== '01') {
              await this.billQueue.add(
                'cancel',
                {
                  no: order.no,
                },
                {
                  delay: dayjs().add(1, 'minutes').diff(dayjs()),
                },
              );
              console.log(`充值订单${no}支付状态未知`);
              return halt(`充值订单${no}支付状态未知`);
            }
          }
        }
      } catch (e) {
        console.log('杉徳充值订单查询异常', e);
        return halt('杉徳充值订单查询异常');
      }
    } else {
      try {
        const result = await this.uopService.query(order.no, order.payType);
        console.log(`银商充值订单${no}状态: `, result?.errCode, result?.status);
        if (result?.errCode === 'SUCCESS') {
          if (result?.status === 'UNKNOWN') {
            await this.billQueue.add(
              'cancel',
              {
                no: order.no,
              },
              {
                delay: dayjs().add(1, 'minutes').diff(dayjs()),
              },
            );
            console.log(`充值订单${no}支付状态未知`);
            return halt(`充值订单${no}支付状态未知`);
          }
          if (result?.status === 'TRADE_SUCCESS') {
            console.log(`充值订单${no}已支付不可取消`);
            await this.handleOrderCallback(no);
            return halt(`充值订单${no}已支付不可取消`);
          }
        }
      } catch (e) {
        console.log('银商充值订单查询失败', e);
        return halt('银商充值订单查询失败');
      }
    }
  }
}
